﻿namespace Jhong.Data.Core.Infrastructure
{
    using System;
    using System.Data;
    using System.Reflection.Emit;

    internal class EntityConverter<T> where T : new()
    {
        private static readonly Action<DataRow, T> ECDelegate = CreateEntityConverDelegate<T>();

        private static Action<DataRow, T> CreateEntityConverDelegate<T>()
        {
            var type = EntityContext<T>.Type;
            var columns = EntityContext<T>.Columns;
            var dynamicMethod = new DynamicMethod(Guid.NewGuid().ToString("N") + "_EntityConver", null, new[] { typeof(DataRow), type }, typeof(DataConverter<T>));
            var il = dynamicMethod.GetILGenerator();

            il.DeclareLocal(typeof(DataColumnCollection));
            il.DeclareLocal(typeof(object));
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Callvirt, typeof(DataRow).GetProperty("Table").GetGetMethod());
            il.Emit(OpCodes.Callvirt, typeof(DataTable).GetProperty("Columns").GetGetMethod());
            il.Emit(OpCodes.Stloc_0);

            foreach (var c in columns)
            {
                var columnName = c.ColumnName;
                var label = il.DefineLabel();
                il.Emit(OpCodes.Ldloc_0);
                il.Emit(OpCodes.Ldstr, columnName);
                il.Emit(OpCodes.Callvirt, typeof(DataColumnCollection).GetMethod("Contains"));
                il.Emit(OpCodes.Brfalse_S, label);
                /**/
                il.Emit(OpCodes.Ldarg_1);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldstr, columnName);
                il.Emit(OpCodes.Callvirt, typeof(DataRow).GetMethod("get_Item", new Type[] { typeof(string) }));
                il.Emit(OpCodes.Call, typeof(DataConverter<>).MakeGenericType(c.Property.PropertyType).GetMethod("Convert"));
                il.Emit(OpCodes.Callvirt, c.Property.GetSetMethod());
                /**/
                il.MarkLabel(label);
            }
            il.Emit(OpCodes.Ret);
            return (Action<DataRow, T>)dynamicMethod.CreateDelegate(typeof(Action<DataRow, T>));
        }

        public static T Convert(DataRow dr)
        {
            var entity = new T();
            ECDelegate(dr, entity);
            return entity;
        }
    }
}